package com.fasterxml.jackson.databind.ser.std;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.EnumUtils;

/**
 * 修改<code>public void serialize(Object bean, JsonGenerator gen, SerializerProvider ctxt)</code>方法，判断枚举值情况单独处理
 */
@JacksonStdImpl
public class JsonValueSerializer extends StdSerializer<Object> implements ContextualSerializer, JsonFormatVisitable {
    protected final AnnotatedMember _accessor;
    protected final TypeSerializer _valueTypeSerializer;
    protected final JsonSerializer<Object> _valueSerializer;
    protected final BeanProperty _property;
    protected final JavaType _valueType;
    protected final boolean _forceTypeInformation;
    protected final Set<String> _ignoredProperties;
    protected transient PropertySerializerMap _dynamicSerializers;

    @SuppressWarnings("unchecked")
    public JsonValueSerializer(AnnotatedMember accessor, TypeSerializer vts, JsonSerializer<?> ser, Set<String> ignoredProperties) {
        super(accessor.getType());
        _accessor = accessor;
        _valueType = accessor.getType();
        _valueTypeSerializer = vts;
        _valueSerializer = (JsonSerializer<Object>) ser;
        _property = null;
        _forceTypeInformation = true;
        _ignoredProperties = ignoredProperties;
        _dynamicSerializers = PropertySerializerMap.emptyForProperties();
    }

    @Deprecated
    public JsonValueSerializer(AnnotatedMember accessor, TypeSerializer vts, JsonSerializer<?> ser) {
        this(accessor, vts, ser, Collections.emptySet());
    }

    @Deprecated
    public JsonValueSerializer(AnnotatedMember accessor, JsonSerializer<?> ser) {
        this(accessor, null, ser, Collections.emptySet());
    }

    @SuppressWarnings("unchecked")
    public JsonValueSerializer(JsonValueSerializer src, BeanProperty property, TypeSerializer vts, JsonSerializer<?> ser, boolean forceTypeInfo) {
        super(_notNullClass(src.handledType()));
        _accessor = src._accessor;
        _valueType = src._valueType;
        _valueTypeSerializer = vts;
        _valueSerializer = (JsonSerializer<Object>) ser;
        _property = property;
        _forceTypeInformation = forceTypeInfo;
        _ignoredProperties = src._ignoredProperties;
        _dynamicSerializers = PropertySerializerMap.emptyForProperties();
    }

    public static JsonValueSerializer construct(SerializationConfig config, AnnotatedMember accessor, TypeSerializer vts, JsonSerializer<?> ser) {
        JsonIgnoreProperties.Value ignorals = config.getAnnotationIntrospector().findPropertyIgnoralByName(config, accessor);
        final Set<String> ignoredProperties = ignorals.findIgnoredForSerialization();
        ser = _withIgnoreProperties(ser, ignoredProperties);
        return new JsonValueSerializer(accessor, vts, ser, ignoredProperties);
    }

    @SuppressWarnings("unchecked")
    private static Class<Object> _notNullClass(Class<?> cls) {
        return (cls == null) ? Object.class : (Class<Object>) cls;
    }

    protected JsonValueSerializer withResolved(BeanProperty property, TypeSerializer vts, JsonSerializer<?> ser, boolean forceTypeInfo) {
        if ((_property == property) && (_valueTypeSerializer == vts) && (_valueSerializer == ser) && (forceTypeInfo == _forceTypeInformation)) {
            return this;
        }
        return new JsonValueSerializer(this, property, vts, ser, forceTypeInfo);
    }

    @Override
    public boolean isEmpty(SerializerProvider ctxt, Object bean) {
        Object referenced = _accessor.getValue(bean);
        if (referenced == null) {
            return true;
        }
        JsonSerializer<Object> ser = _valueSerializer;
        if (ser == null) {
            try {
                ser = _findDynamicSerializer(ctxt, referenced.getClass());
            } catch (JsonMappingException e) {
                throw new RuntimeJsonMappingException(e);
            }
        }
        return ser.isEmpty(ctxt, referenced);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider ctxt, BeanProperty property) throws JsonMappingException {
        TypeSerializer typeSer = _valueTypeSerializer;
        if (typeSer != null) {
            typeSer = typeSer.forProperty(property);
        }
        JsonSerializer<?> ser = _valueSerializer;
        if (ser == null) {
            if (ctxt.isEnabled(MapperFeature.USE_STATIC_TYPING) || _valueType.isFinal()) {
                ser = ctxt.findPrimaryPropertySerializer(_valueType, property);
                ser = _withIgnoreProperties(ser, _ignoredProperties);
                boolean forceTypeInformation = isNaturalTypeWithStdHandling(_valueType.getRawClass(), ser);
                return withResolved(property, typeSer, ser, forceTypeInformation);
            }

            if (property != _property) {
                return withResolved(property, typeSer, ser, _forceTypeInformation);
            }
        } else {
            ser = ctxt.handlePrimaryContextualization(ser, property);
            return withResolved(property, typeSer, ser, _forceTypeInformation);
        }
        return this;
    }

    @Override
    public void serialize(Object bean, JsonGenerator gen, SerializerProvider ctxt) throws IOException {
        Object value;
        try {
            // 单独处理枚举类的值，取枚举本身的值，而不是内部定义的其他值
            value = bean instanceof Enum ? EnumUtils.serialize((Enum<? extends Enum<?>>) bean, ctxt.getConfig()).getValue() : _accessor.getValue(bean);
        } catch (Exception e) {
            value = null;
            wrapAndThrow(ctxt, e, bean, _accessor.getName() + "()");
        }

        if (value == null) {
            ctxt.defaultSerializeNull(gen);
        } else {
            JsonSerializer<Object> ser = _valueSerializer;
            if (ser == null) {
                ser = _findDynamicSerializer(ctxt, value.getClass());
            }
            if (_valueTypeSerializer != null) {
                ser.serializeWithType(value, gen, ctxt, _valueTypeSerializer);
            } else {
                ser.serialize(value, gen, ctxt);
            }
        }
    }

    @Override
    public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider ctxt, TypeSerializer typeSer0) throws IOException {
        Object value;
        try {
            // 单独处理枚举类的值，取枚举本身的值，而不是内部定义的其他值
            value = bean instanceof Enum ? EnumUtils.serialize((Enum<? extends Enum<?>>) bean, ctxt.getConfig()).getValue() : _accessor.getValue(bean);
        } catch (Exception e) {
            value = null;
            wrapAndThrow(ctxt, e, bean, _accessor.getName() + "()");
        }

        if (value == null) {
            ctxt.defaultSerializeNull(gen);
            return;
        }
        JsonSerializer<Object> ser = _valueSerializer;
        if (ser == null) {
            ser = _findDynamicSerializer(ctxt, value.getClass());
        } else {
            if (_forceTypeInformation) {
                WritableTypeId typeIdDef = typeSer0.writeTypePrefix(gen, typeSer0.typeId(bean, JsonToken.VALUE_STRING));
                ser.serialize(value, gen, ctxt);
                typeSer0.writeTypeSuffix(gen, typeIdDef);
                return;
            }
        }

        TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
        ser.serializeWithType(value, gen, ctxt, rr);
    }

    @Deprecated
    @Override
    public JsonNode getSchema(SerializerProvider ctxt, Type typeHint) throws JsonMappingException {
        if (_valueSerializer instanceof com.fasterxml.jackson.databind.jsonschema.SchemaAware) {
            return ((com.fasterxml.jackson.databind.jsonschema.SchemaAware) _valueSerializer).getSchema(ctxt, null);
        }
        return com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode();
    }

    @Override
    public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
        Class<?> declaring = _accessor.getDeclaringClass();
        if ((declaring != null) && ClassUtil.isEnumType(declaring)) {
            if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) {
                return;
            }
        }
        JsonSerializer<Object> ser = _valueSerializer;
        if (ser == null) {
            ser = visitor.getProvider().findTypedValueSerializer(_valueType, false, _property);
            if (ser == null) {
                visitor.expectAnyFormat(typeHint);
                return;
            }
        }
        ser.acceptJsonFormatVisitor(visitor, _valueType);
    }

    protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visitor, JavaType typeHint, Class<?> enumType) throws JsonMappingException {
        JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
        if (stringVisitor != null) {
            Set<String> enums = new LinkedHashSet<>();
            for (Object en : enumType.getEnumConstants()) {
                try {
                    enums.add(String.valueOf(_accessor.getValue(en)));
                } catch (Exception e) {
                    Throwable t = e;
                    while (t instanceof InvocationTargetException && t.getCause() != null) {
                        t = t.getCause();
                    }
                    ClassUtil.throwIfError(t);
                    throw JsonMappingException.wrapWithPath(t, en, _accessor.getName() + "()");
                }
            }
            stringVisitor.enumTypes(enums);
        }
        return true;
    }

    protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<?> ser) {
        if (rawType.isPrimitive()) {
            if (rawType != Integer.TYPE && rawType != Boolean.TYPE && rawType != Double.TYPE) {
                return false;
            }
        } else {
            if (rawType != String.class && rawType != Integer.class && rawType != Boolean.class && rawType != Double.class) {
                return false;
            }
        }
        return isDefaultSerializer(ser);
    }

    protected JsonSerializer<Object> _findDynamicSerializer(SerializerProvider ctxt, Class<?> valueClass) throws JsonMappingException {
        JsonSerializer<Object> serializer = _dynamicSerializers.serializerFor(valueClass);
        if (serializer == null) {
            if (_valueType.hasGenericTypes()) {
                final JavaType fullType = ctxt.constructSpecializedType(_valueType, valueClass);
                serializer = ctxt.findPrimaryPropertySerializer(fullType, _property);
                serializer = _withIgnoreProperties(serializer, _ignoredProperties);
                PropertySerializerMap.SerializerAndMapResult result = _dynamicSerializers.addSerializer(fullType, serializer);
                _dynamicSerializers = result.map;
            } else {
                serializer = ctxt.findPrimaryPropertySerializer(valueClass, _property);
                serializer = _withIgnoreProperties(serializer, _ignoredProperties);
                PropertySerializerMap.SerializerAndMapResult result = _dynamicSerializers.addSerializer(valueClass, serializer);
                _dynamicSerializers = result.map;
            }
        }
        return serializer;
    }

    @SuppressWarnings("unchecked")
    protected static JsonSerializer<Object> _withIgnoreProperties(JsonSerializer<?> ser, Set<String> ignoredProperties) {
        if (ser != null) {
            if (!ignoredProperties.isEmpty()) {
                ser = ser.withIgnoredProperties(ignoredProperties);
            }
        }
        return (JsonSerializer<Object>) ser;
    }

    @Override
    public String toString() {
        return "(@JsonValue serializer for method " + _accessor.getDeclaringClass() + "#" + _accessor.getName() + ")";
    }

    static class TypeSerializerRerouter extends TypeSerializer {
        protected final TypeSerializer _typeSerializer;
        protected final Object _forObject;

        public TypeSerializerRerouter(TypeSerializer ts, Object ob) {
            _typeSerializer = ts;
            _forObject = ob;
        }

        @Override
        public TypeSerializer forProperty(BeanProperty prop) {
            throw new UnsupportedOperationException();
        }

        @Override
        public As getTypeInclusion() {
            return _typeSerializer.getTypeInclusion();
        }

        @Override
        public String getPropertyName() {
            return _typeSerializer.getPropertyName();
        }

        @Override
        public TypeIdResolver getTypeIdResolver() {
            return _typeSerializer.getTypeIdResolver();
        }

        @Override
        public WritableTypeId writeTypePrefix(JsonGenerator g, WritableTypeId typeId) throws IOException {
            typeId.forValue = _forObject;
            return _typeSerializer.writeTypePrefix(g, typeId);
        }

        @Override
        public WritableTypeId writeTypeSuffix(JsonGenerator g, WritableTypeId typeId) throws IOException {
            return _typeSerializer.writeTypeSuffix(g, typeId);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForScalar(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypePrefixForScalar(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForObject(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypePrefixForObject(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForArray(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypePrefixForArray(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypeSuffixForScalar(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypeSuffixForScalar(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypeSuffixForObject(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypeSuffixForObject(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypeSuffixForArray(Object value, JsonGenerator gen) throws IOException {
            _typeSerializer.writeTypeSuffixForArray(_forObject, gen);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForScalar(Object value, JsonGenerator gen, Class<?> type) throws IOException {
            _typeSerializer.writeTypePrefixForScalar(_forObject, gen, type);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForObject(Object value, JsonGenerator gen, Class<?> type) throws IOException {
            _typeSerializer.writeTypePrefixForObject(_forObject, gen, type);
        }

        @Override
        @Deprecated
        public void writeTypePrefixForArray(Object value, JsonGenerator gen, Class<?> type) throws IOException {
            _typeSerializer.writeTypePrefixForArray(_forObject, gen, type);
        }

        @Override
        @Deprecated
        public void writeCustomTypePrefixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypePrefixForScalar(_forObject, gen, typeId);
        }

        @Override
        @Deprecated
        public void writeCustomTypePrefixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypePrefixForObject(_forObject, gen, typeId);
        }

        @Override
        @Deprecated
        public void writeCustomTypePrefixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypePrefixForArray(_forObject, gen, typeId);
        }

        @Override
        @Deprecated
        public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypeSuffixForScalar(_forObject, gen, typeId);
        }

        @Override
        @Deprecated
        public void writeCustomTypeSuffixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypeSuffixForObject(_forObject, gen, typeId);
        }

        @Override
        @Deprecated
        public void writeCustomTypeSuffixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
            _typeSerializer.writeCustomTypeSuffixForArray(_forObject, gen, typeId);
        }
    }
}
